package com.koelo.kiss.re.interpreter.script;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;

import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.log4j.Logger;

import com.koelo.kiss.re.interpreter.AbstractInterpreter;
import com.koelo.kiss.re.interpreter.InterpreterException;
import com.koelo.kiss.re.parser.IParser;
import com.koelo.kiss.re.util.Context;
import com.koelo.kiss.re.util.IContext;
import com.koelo.kiss.re.util.IMessage;
import com.koelo.kiss.re.util.IRule;
import com.koelo.kiss.re.util.ISession;

public abstract class AbstractScriptInterpreter 
	extends AbstractInterpreter {

	private static Logger LOGGER = Logger.getLogger(AbstractScriptInterpreter.class);
	
	private ScriptEngine scriptEngine;
	private String source;

	public AbstractScriptInterpreter(String source, IContext context, IParser parser) {

		super(context, parser);
		setSource(source);
		setScriptEngine(getName());
	}

	public AbstractScriptInterpreter(String source, IParser parser) {

		this(source, new Context(), parser);
	}

	private void setScriptEngine(String name) {

		this.scriptEngine = new ScriptEngineManager().getEngineByName(name);
		if (this.scriptEngine == null) {
			throw new InterpreterException("Cannot create script engine (" + name + "). Interpreter not found.");
		}
	}
	
	protected final void setSource(String source) {
		
		this.source = source;
	}
	
	protected final String getSource() {
		
		return this.source == null ? "" : this.source.endsWith("/") ? 
			this.source : this.source.endsWith("\\") ? this.source : this.source + "/";
	}
	
	@Override
	public final IRule getResource(String name) 
		throws InterpreterException {
		
		try {
			LOGGER.info("Loading rule object " + name + "." + getExtension() + ". wait.");
			CompiledScript script = ((Compilable) this.scriptEngine).compile(new InputStreamReader(new FileInputStream(getSource() + name + "." + getExtension())));
			LOGGER.info("Loading rule object " + name + "." + getExtension() + ". done.");
			if (isHotSwapMode()) {
				return new HotRule(getSource(), name, getExtension(), script);
			} else {
				return new Rule(name, getExtension(), script);
			}
		} catch (FileNotFoundException ex) {
			throw new InterpreterException(ex);
		} catch (ScriptException ex) {
			throw new InterpreterException(ex);
		}
	}
}

class Rule
	implements IRule {

	private static Logger LOGGER = Logger.getLogger(Rule.class);

	private final String name;
	private final String extension;
	private final CompiledScript script;
	
	public Rule(String name, String extension, CompiledScript script) {
	
		this.name = name;
		this.extension = extension;
		this.script = script;
	}
	
	public final String getName() {
		
		return this.name;
	}
	
	public final String getExtension() {
		
		return this.extension;
	}
	
	public CompiledScript getScript() {
		
		return this.script;
	}
	
	public final boolean eval(IContext context, ISession session, IMessage message) 
		throws InterpreterException {

		try {
			LOGGER.info("Evaluating rule object " + getName() + "." + getExtension() + ". wait.");
			getScript().eval();
			Object result = ((Invocable) getScript().getEngine()).invokeFunction("eval", context, session, message);
			LOGGER.info("Evaluating rule object " + getName() + "." + getExtension() + ". done.");
			if (result instanceof Boolean) {
				return (Boolean) result;
			}
			return false;
		} catch (ScriptException ex) {
			throw new InterpreterException(ex);
		} catch (NoSuchMethodException ex) {
			throw new InterpreterException(ex);
		}
	}
}

class HotRule
	extends Rule {
	
	private String source;
	
	public HotRule(String source, String name, String extension, CompiledScript script) {
		
		super(name, extension, script);
		this.source = source;
	}
	
	public final String getSource() {
		
		return this.source;
	}
	
	public CompiledScript getScript() {
		
		try {
			ScriptEngine engine = super.getScript().getEngine();
			CompiledScript script = ((Compilable) engine).compile(new InputStreamReader(new FileInputStream(getSource() + getName() + "." + getExtension())));
			return script;
		} catch (ScriptException ex) {
			throw new RuntimeException(ex.getMessage(), ex);
		} catch (FileNotFoundException ex) {
			throw new RuntimeException(ex.getMessage(), ex);
		}
	}
}